﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;

using BizElements.Core;

namespace BizElements.BusinessLayer
{
    /// <summary>Serializes entity primary key fields by concatenating them into strings in the order as returned by entity's <see cref="BizElements.Core.EntityExtensions.GetPrimaryKeyValue"/> method.</summary>
    public sealed class PrimaryKeyConcatenator : IPrimaryKeySerializer
    {
        #region Configuration.

        /// <summary>Default delimiter character: #</summary>
        public static readonly char DefaultDelimiter = '#';

        /// <summary>Message formatter with invariant culture.</summary>
        public static readonly IMessageFormatter DefaultFormatter = new MessageFormatter(CultureInfo.InvariantCulture);

        #endregion

        #region Properties.

        /// <summary>Gets or sets delimiter character that separates composite key parts.</summary>
        /// <value>Delimiter character. If not set then the value configured in <see cref="DefaultDelimiter"/> will be used.</value>
        public char? Delimiter { get; set; }

        /// <summary>Gets or sets the object that provides formats for primary key values.</summary>
        /// <value>Formatter. If not set then <see cref="DefaultFormatter"/> will be used.</value>
        public IMessageFormatter Formatter { get; set; }

        #endregion

        #region Methods.

        /// <summary>Concatenates the specified entity's primary key.</summary>
        /// <param name="entity">Entity whose primary key is to be serialized.</param>
        /// <returns>Concatenated primary key fields.</returns>
        public string Serialize(IEntity entity)
        {
            object[] pkValues = entity.GetPrimaryKeyValue();
            char delimiter = this.Delimiter ?? DefaultDelimiter;
            IMessageFormatter formatter = this.Formatter ?? DefaultFormatter;
            string concatenatedPk = ArrayUtil.Concat(pkValues, delimiter.ToString(), formatter);
            return concatenatedPk;
        }

        /// <summary>Extracts and serializes primary key of all entities in the specified collection.</summary>
        /// <param name="entities">Entities whose primary key are to be serialized.</param>
        /// <returns>Array of serialized primary keys.</returns>
        public string[] Serialize(IEnumerable entities)
        {
            List<string> serializedPKs = new List<string>();
            foreach (IEntity entity in entities)
                serializedPKs.Add(Serialize(entity));

            return serializedPKs.ToArray();
        }

        /// <summary>Parses primary key from the specified string and sets the provided entity's primary key properties.</summary>
        /// <param name="entity">Entity whose primary key properties are to be deserialized and set.</param>
        /// <param name="serializedPrimaryKey">Serialized primary key.</param>
        public void Deserialize(IEntity entity, string serializedPrimaryKey)
        {
            char delimiter = this.Delimiter ?? DefaultDelimiter;
            string[] splitValues = serializedPrimaryKey.Split(delimiter);
            var pkColumns = entity.Table.PrimaryKey;
            if (splitValues.Length != pkColumns.Count)
                throw new ArgumentException("Value count mismatch. Expected " + pkColumns.Count + " but received " + splitValues.Length, "serializedPrimaryKey");

            IMessageFormatter formatter = this.Formatter ?? DefaultFormatter;
            for (int i = 0; i < splitValues.Length; i++)
            {
                IDbColumn pkPart = pkColumns[i];
                object pkPartValue = NumberParser.ParseValue(pkPart.DataType, splitValues[i], formatter);
                if (pkPartValue == null)
                    throw new ArgumentException("Cannot parse primary key values from the provided string: " + serializedPrimaryKey, "serializedPrimaryKey");

                entity.SetField(pkPart, pkPartValue);
            }
        }

        #endregion
    }
}
